from fastapi import APIRouter, BackgroundTasks, HTTPException
from typing import Dict, List, Optional
import json
import logging
from datetime import datetime, timedelta
import asyncio
from pydantic import BaseModel
from fastapi import Request
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy import select, and_, func, delete
from app.services.db import SessionLocal, engine
from app.models.quant_order import QuantOrder, OrderStatus
from app.models.strategy_token import StrategyToken
import hashlib
import os
import secrets
from pathlib import Path
from app.services.stocks import get_stock_detail, update_cn_names_mairui_db
from app.models.stock_dict import StockDict
from sqlalchemy import select

router = APIRouter()

# 数据模型
class TradingSignal(BaseModel):
    tunnelId: str
    data: dict
    timestamp: datetime = None

class PollRequest(BaseModel):
    client_id: str
    key: str
    tunnelId: Optional[str] = None
    wait: int = 0

class ConfirmRequest(BaseModel):
    client_id: str
    order_id: str
    status: str
    tunnelId: Optional[str] = None

# 辅助函数（数据库会话与哈希）
def _compute_hash(tunnel_id: str, payload: dict) -> str:
    try:
        canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
    except Exception:
        canonical = str(payload)
    return hashlib.sha256((tunnel_id + "|" + canonical).encode("utf-8")).hexdigest()

def _get_tunnel_by_token(tok: str) -> Optional[str]:
    s = SessionLocal()
    try:
        stmt = select(StrategyToken.tunnel_id).where(StrategyToken.token == tok).where(StrategyToken.active == True).limit(1)
        row = s.execute(stmt).first()
        return row[0] if row else None
    finally:
        s.close()

def _get_tunnel_by_id(token_id: int) -> Optional[str]:
    s = SessionLocal()
    try:
        stmt = select(StrategyToken.tunnel_id).where(StrategyToken.id == int(token_id)).where(StrategyToken.active == True).limit(1)
        row = s.execute(stmt).first()
        return row[0] if row else None
    finally:
        s.close()

def _verify_token(tok: str) -> bool:
    return bool(_get_tunnel_by_token(tok))

def _verify_admin(http_req: Request) -> bool:
    auth = http_req.headers.get("authorization") or ""
    token_hdr = auth.split(" ", 1)[1].strip() if auth.lower().startswith("bearer ") else ""
    admin_token = os.environ.get("QUANT_ADMIN_TOKEN", "")
    admin_pass = os.environ.get("QUANT_ADMIN_PASS", "")
    cookie_val = http_req.cookies.get("quant_admin_session") or ""
    if admin_token and token_hdr == admin_token:
        return True
    if admin_pass and (cookie_val == admin_pass or token_hdr == admin_pass):
        return True
    return False

@router.post("/signal")
async def receive_signal(data: dict):
    """
    接收来自聚宽的交易信号 -> 入库为 pending，避免重复
    """
    try:
        if "method" not in data or data["method"] != "sendMsg":
            raise HTTPException(status_code=400, detail="无效的信号格式")
        auth_token = None
        if isinstance(data, dict):
            auth_token = data.get("key") or data.get("token")
        tunnelId = None
        if auth_token and _verify_token(auth_token):
            tunnelId = _get_tunnel_by_token(auth_token)
        else:
            tunnelId = data.get("tunnelId", "default")
        signal_data = json.loads(data.get("data", "{}"))
        now_ts = datetime.now().isoformat()
        content_hash = _compute_hash(tunnelId, signal_data)
        # 入库（去重：pending/processing 不重复）
        session = SessionLocal()
        try:
            exists_stmt = select(QuantOrder.id).where(
                and_(
                    QuantOrder.tunnel_id == tunnelId,
                    QuantOrder.content_hash == content_hash,
                    QuantOrder.status.in_([OrderStatus.pending, OrderStatus.processing])
                )
            )
            exists = session.execute(exists_stmt).first()
            if exists:
                return {
                    "status": "success",
                    "message": "重复信号已忽略（pending/processing）",
                    "tunnelId": tunnelId
                }
            # 创建订单
            order = QuantOrder(
                tunnel_id=tunnelId,
                data=json.dumps(signal_data, ensure_ascii=False),
                content_hash=content_hash,
                status=OrderStatus.pending,
                order_timestamp=now_ts
            )
            session.add(order)
            session.commit()
            return {
                "status": "success",
                "message": "信号接收成功",
                "tunnelId": tunnelId
            }
        finally:
            session.close()
    except Exception as e:
        logging.error(f"处理信号失败: {e}")
        raise HTTPException(status_code=500, detail="信号处理失败")

@router.post("/qmt_poll")
async def poll_orders(request: PollRequest, http_req: Request):
    """
    QMT客户端轮询获取订单：支持长轮询，使用 PostgreSQL 行级锁避免并发重复派发。
    - 若 wait>0，则在最长 wait 秒内等待订单出现；找到后立即返回并置为 processing
    - 使用 SELECT ... FOR UPDATE SKIP LOCKED 锁定最早 pending 的订单，避免并发抢同一条（仅 PostgreSQL）
    """
    try:
        auth = http_req.headers.get("authorization") or ""
        token = request.key
        if auth.lower().startswith("bearer "):
            token = auth.split(" ", 1)[1].strip()
        if not token or not _verify_token(token):
            return {"status": "error", "message": "认证失败"}
        tunnel = _get_tunnel_by_token(token) or request.tunnelId
        if not tunnel:
            return {"status": "error", "message": "缺少隧道标识"}
        # 规范化等待时长（建议上限 30s）
        wait_seconds = max(0, min(int(request.wait or 0), 30))
        deadline = datetime.now() + timedelta(seconds=wait_seconds)
        
        while True:
            session = SessionLocal()
            try:
                # 选取最早的 pending 订单
                stmt = (
                    select(QuantOrder)
                    .where(
                        and_(
                            QuantOrder.tunnel_id == tunnel,
                            QuantOrder.status == OrderStatus.pending,
                        )
                    )
                    .order_by(QuantOrder.created_at.asc())
                    .limit(1)
                )
                # PostgreSQL: FOR UPDATE SKIP LOCKED（仅在支持时启用）
                try:
                    if getattr(engine, "dialect", None) and engine.dialect.name == "postgresql":
                        stmt = stmt.with_for_update(skip_locked=True)
                except Exception:
                    # 非支持方言或语法异常时，静默降级为普通查询
                    pass
                row = session.execute(stmt).scalars().first()
                if row:
                    row.status = OrderStatus.processing
                    row.processed_time = datetime.now()
                    row.client_id = request.client_id
                    session.commit()
                    return {
                        "status": "success",
                        "order": {
                            "order": json.loads(row.data),
                            "timestamp": row.order_timestamp,
                        },
                        "message": "获取到交易信号",
                    }
                # 若未找到订单，依据长轮询策略等待
                if datetime.now() >= deadline:
                    return {
                        "status": "success",
                        "order": None,
                        "message": "暂无交易信号",
                    }
                # 小憩 1 秒后继续尝试，避免阻塞事件循环过久
                await asyncio.sleep(1)
            finally:
                session.close()
    except Exception as e:
        logging.error(f"轮询失败: {e}")
        return {"status": "error", "message": "轮询处理失败"}

@router.post("/qmt_confirm")
async def confirm_order(request: ConfirmRequest, http_req: Request):
    """
    QMT客户端确认订单执行：根据 tunnelId + order_id(=timestamp) 定位 processing 订单并更新状态
    """
    try:
        auth = http_req.headers.get("authorization") or ""
        token = ""
        if auth.lower().startswith("bearer "):
            token = auth.split(" ", 1)[1].strip()
        if not token or not _verify_token(token):
            return {"status": "error", "message": "认证失败"}
        tunnel = _get_tunnel_by_token(token) or request.tunnelId
        session = SessionLocal()
        try:
            stmt = select(QuantOrder).where(
                and_(
                    QuantOrder.tunnel_id == tunnel,
                    QuantOrder.order_timestamp == request.order_id,
                    QuantOrder.status == OrderStatus.processing
                )
            ).limit(1)
            order = session.execute(stmt).scalars().first()
            if not order:
                return {"status": "error", "message": "未找到待确认的订单"}
            # 更新状态
            new_status = OrderStatus(request.status) if request.status in OrderStatus.__members__.values() else None
            if not new_status:
                # 兼容字符串
                if request.status in ["executed", "failed"]:
                    new_status = OrderStatus(request.status)
                else:
                    new_status = OrderStatus.failed
            order.status = new_status
            order.confirmed_time = datetime.now()
            session.commit()
            return {"status": "success", "message": "确认接收成功"}
        finally:
            session.close()
    except Exception as e:
        logging.error(f"确认失败: {e}")
        return {"status": "error", "message": "确认处理失败"}

@router.get("/signals")
async def get_signal_history(tunnelId: Optional[str] = None, limit: int = 50):
    """获取信号历史（来自数据库）"""
    session = SessionLocal()
    try:
        stmt = select(QuantOrder).order_by(QuantOrder.created_at.asc())
        if tunnelId:
            stmt = stmt.where(QuantOrder.tunnel_id == tunnelId)
        rows = session.execute(stmt).scalars().all()
        history = [
            {
                "tunnelId": r.tunnel_id,
                "data": json.loads(r.data),
                "timestamp": r.order_timestamp,
                "status": r.status.value,
                "processed_time": r.processed_time.isoformat() if r.processed_time else None,
                "confirmed_time": r.confirmed_time.isoformat() if r.confirmed_time else None,
            }
            for r in rows[-limit:]
        ]
        return {"total": len(rows), "signals": history}
    finally:
        session.close()

@router.get("/status")
async def get_system_status():
    """获取系统状态（来自数据库）"""
    session = SessionLocal()
    try:
        # pending 按隧道计数
        pending_counts = {}
        stmt = select(QuantOrder.tunnel_id, func.count(QuantOrder.id)).where(QuantOrder.status == OrderStatus.pending).group_by(QuantOrder.tunnel_id)
        for tunnel_id, cnt in session.execute(stmt).all():
            pending_counts[tunnel_id] = cnt
        # 活跃客户端：过去24小时有处理记录的去重client
        cutoff = datetime.now() - timedelta(hours=24)
        stmt_clients = select(func.count(func.distinct(QuantOrder.client_id))).where(QuantOrder.processed_time != None).where(QuantOrder.processed_time > cutoff)
        active_clients = session.execute(stmt_clients).scalar() or 0
        # 总历史
        total_history = session.execute(select(func.count(QuantOrder.id))).scalar() or 0
        return {
            "pending_signals": pending_counts,
            "active_clients": active_clients,
            "total_history": total_history
        }
    finally:
        session.close()

class AdminCreateToken(BaseModel):
    tunnelId: Optional[str] = None
    remark: Optional[str] = None

class AdminUpdateRemark(BaseModel):
    remark: str

class AdminUpdateActive(BaseModel):
    active: bool

@router.get("/admin/login", response_class=HTMLResponse)
async def admin_login_page(request: Request):
    return templates.TemplateResponse("quant_admin_login.html", {"request": request})

class AdminLogin(BaseModel):
    username: str
    password: str

@router.post("/admin/login")
async def admin_login(req: AdminLogin):
    user = os.environ.get("QUANT_ADMIN_USER", "admin")
    pw = os.environ.get("QUANT_ADMIN_PASS", os.environ.get("QUANT_ADMIN_TOKEN", ""))
    if not pw:
        raise HTTPException(status_code=500, detail="Admin credentials not set")
    if req.username != user or req.password != pw:
        raise HTTPException(status_code=401, detail="Unauthorized")
    resp = RedirectResponse(url="/api/quant/admin/home", status_code=302)
    resp.set_cookie("quant_admin_session", pw, httponly=True, samesite="lax")
    return resp

@router.get("/admin", response_class=HTMLResponse)
async def admin_tokens_page(request: Request):
    if not _verify_admin(request):
        return RedirectResponse(url="/api/quant/admin/login")
    return templates.TemplateResponse("quant_admin_tokens.html", {"request": request})

@router.get("/admin/home", response_class=HTMLResponse)
async def admin_home_page(request: Request):
    if not _verify_admin(request):
        return RedirectResponse(url="/api/quant/admin/login")
    return templates.TemplateResponse("quant_admin_home.html", {"request": request})

@router.get("/admin/orders", response_class=HTMLResponse)
async def admin_orders_page(request: Request):
    if not _verify_admin(request):
        return RedirectResponse(url="/api/quant/admin/login")
    return templates.TemplateResponse("quant_admin_orders.html", {"request": request})

@router.get("/admin/orders/data")
async def admin_orders_data(request: Request, token_id: Optional[int] = None, status: Optional[str] = None, page: int = 1, page_size: int = 50):
    if not _verify_admin(request):
        raise HTTPException(status_code=403, detail="Forbidden")
    s = SessionLocal()
    try:
        tunnel = None
        if token_id is not None:
            tunnel = _get_tunnel_by_id(int(token_id))
        if not tunnel:
            raise HTTPException(status_code=400, detail="缺少或无效的令牌")
        page = max(1, int(page or 1))
        page_size = max(1, min(int(page_size or 50), 200))
        offset = (page - 1) * page_size
        # 过滤条件
        count_stmt = select(func.count(QuantOrder.id)).where(QuantOrder.tunnel_id == tunnel)
        data_stmt = select(QuantOrder).where(QuantOrder.tunnel_id == tunnel)
        if status and status in {"pending","processing","executed","failed","expired"}:
            count_stmt = count_stmt.where(QuantOrder.status == OrderStatus(status))
            data_stmt = data_stmt.where(QuantOrder.status == OrderStatus(status))
        total = s.execute(count_stmt).scalar() or 0
        stmt = data_stmt.order_by(QuantOrder.created_at.desc()).offset(offset).limit(page_size)
        rows = s.execute(stmt).scalars().all()
        def parse_row(r):
            try:
                d = json.loads(r.data)
            except Exception:
                d = {}
            code_raw = d.get("orderCode") or d.get("security") or ""
            digits = "".join(ch for ch in str(code_raw) if ch.isdigit())
            code = digits[:6] if len(digits) >= 6 else str(code_raw)[:6]
            vol = d.get("volume")
            side = None
            try:
                if vol is not None:
                    side = "买入" if float(vol) > 0 else "卖出"
            except Exception:
                side = None
            cr = str(code_raw).lower()
            exch = ""
            if cr.endswith(".sh") or cr.startswith("sh") or (code and code.startswith("6")):
                exch = "SH"
            elif cr.endswith(".sz") or cr.startswith("sz") or (code and (code.startswith("0") or code.startswith("3"))):
                exch = "SZ"
            return {
                "code": code,
                "exchange": exch,
                "side": side,
                "volume": vol,
                "status": r.status.value,
                "created_at": r.created_at.isoformat() if r.created_at else None,
                "processed_time": r.processed_time.isoformat() if r.processed_time else None,
                "confirmed_time": r.confirmed_time.isoformat() if r.confirmed_time else None,
                "raw": d,
                "order_timestamp": r.order_timestamp,
            }
        items = [parse_row(r) for r in rows]
        pages = (total + page_size - 1) // page_size
        return {"tunnelId": tunnel, "items": items, "total": total, "page": page, "page_size": page_size, "pages": pages}
    finally:
        s.close()

@router.get("/admin/stocks/dict")
async def admin_stocks_dict(http_req: Request):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    s = SessionLocal()
    try:
        rows = s.execute(select(StockDict.code, StockDict.name)).all()
        return {k: v for k, v in rows}
    finally:
        s.close()

# 股票详情页面与数据接口移除

 

class AdminPurgeOrders(BaseModel):
    token_id: Optional[int] = None
    older_than_days: Optional[int] = None
    all: Optional[bool] = False
    status: Optional[str] = None

@router.post("/admin/orders/purge")
async def admin_purge_orders(http_req: Request, req: AdminPurgeOrders):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    if not req.token_id:
        raise HTTPException(status_code=400, detail="缺少令牌ID")
    tunnel = _get_tunnel_by_id(int(req.token_id))
    if not tunnel:
        raise HTTPException(status_code=400, detail="无效令牌")
    s = SessionLocal()
    try:
        conditions = [QuantOrder.tunnel_id == tunnel]
        if not req.all:
            statuses = None
            if req.status == "completed":
                statuses = [OrderStatus.executed, OrderStatus.failed, OrderStatus.expired]
            elif req.status in {"pending","processing","executed","failed","expired"}:
                statuses = [OrderStatus(req.status)]
            else:
                statuses = [OrderStatus.executed, OrderStatus.failed, OrderStatus.expired]
            conditions.append(QuantOrder.status.in_(statuses))
        if req.older_than_days and req.older_than_days > 0:
            cutoff = datetime.now() - timedelta(days=int(req.older_than_days))
            conditions.append(QuantOrder.created_at < cutoff)
        stmt = delete(QuantOrder).where(and_(*conditions))
        result = s.execute(stmt)
        s.commit()
        return {"status": "success", "deleted": result.rowcount or 0}
    finally:
        s.close()

@router.post("/admin/token")
async def admin_create_token(req: AdminCreateToken, http_req: Request):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    s = SessionLocal()
    try:
        cnt = s.execute(select(func.count(StrategyToken.id))).scalar() or 0
        remark = req.remark or f"未命名客户端{cnt + 1}"
        tunnel = req.tunnelId or f"default-{cnt + 1}"
        tok = secrets.token_urlsafe(24)
        obj = StrategyToken(token=tok, tunnel_id=tunnel, remark=remark, active=True)
        s.add(obj)
        s.commit()
        return {"id": obj.id, "token": tok, "tunnelId": tunnel, "remark": remark}
    finally:
        s.close()

@router.get("/admin/tokens")
async def admin_list_tokens(http_req: Request):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    s = SessionLocal()
    try:
        stmt = select(StrategyToken.id, StrategyToken.token, StrategyToken.tunnel_id, StrategyToken.remark, StrategyToken.active, StrategyToken.created_at)
        rows = s.execute(stmt).all()
        return {"items": [{"id": i, "token": t, "tunnelId": u, "remark": r, "active": a, "created_at": c.isoformat() if c else None} for i, t, u, r, a, c in rows]}
    finally:
        s.close()

@router.delete("/admin/token/id/{id}")
async def admin_delete_token_by_id(id: int, http_req: Request, hard: bool = False):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    s = SessionLocal()
    try:
        stmt = select(StrategyToken).where(StrategyToken.id == int(id)).limit(1)
        obj = s.execute(stmt).scalars().first()
        if not obj:
            raise HTTPException(status_code=404, detail="Not Found")
        if hard:
            s.delete(obj)
        else:
            obj.active = False
        s.commit()
        return {"status": "success"}
    finally:
        s.close()

@router.patch("/admin/token/id/{id}/remark")
async def admin_update_remark_by_id(id: int, req: AdminUpdateRemark, http_req: Request):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    s = SessionLocal()
    try:
        stmt = select(StrategyToken).where(StrategyToken.id == int(id)).limit(1)
        obj = s.execute(stmt).scalars().first()
        if not obj:
            raise HTTPException(status_code=404, detail="Not Found")
        obj.remark = req.remark
        s.commit()
        return {"status": "success"}
    finally:
        s.close()

@router.patch("/admin/token/id/{id}/active")
async def admin_update_active_by_id(id: int, req: AdminUpdateActive, http_req: Request):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    s = SessionLocal()
    try:
        stmt = select(StrategyToken).where(StrategyToken.id == int(id)).limit(1)
        obj = s.execute(stmt).scalars().first()
        if not obj:
            raise HTTPException(status_code=404, detail="Not Found")
        obj.active = req.active
        s.commit()
        return {"status": "success"}
    finally:
        s.close()

# 后台清理任务（将过期 pending/processing 标记为 expired）
async def cleanup_task():
    while True:
        await asyncio.sleep(3600)
        session = SessionLocal()
        try:
            cutoff_time = datetime.now() - timedelta(hours=24)
            stmt = select(QuantOrder).where(
                and_(
                    QuantOrder.created_at < cutoff_time,
                    QuantOrder.status.in_([OrderStatus.pending, OrderStatus.processing])
                )
            )
            rows = session.execute(stmt).scalars().all()
            for r in rows:
                r.status = OrderStatus.expired
            session.commit()
        except Exception as e:
            logging.error(f"清理过期信号失败: {e}")
        finally:
            session.close()

@router.on_event("startup")
async def startup_event():
    asyncio.create_task(cleanup_task())
    asyncio.create_task(stocks_update_task())
    asyncio.create_task(orders_purge_task())
# 添加模板配置
templates = Jinja2Templates(directory="app/templates")

@router.get("/monitor", response_class=HTMLResponse)
async def quant_monitor(request: Request):
    """量化监控页面"""
    try:
        # 优先使用模板目录中的页面
        return templates.TemplateResponse("quant_monitor.html", {"request": request})
    except Exception:
        # 回退到静态文件 app/static/quant.html
        import pathlib
        p = pathlib.Path("app/static/quant.html")
        if p.exists():
            return HTMLResponse(content=p.read_text(encoding="utf-8"))
        raise HTTPException(status_code=404, detail="量化监控页面不存在")

async def stocks_update_task():
    while True:
        try:
            await asyncio.sleep(86400)
            update_cn_names_mairui_db()
        except Exception:
            pass

async def orders_purge_task():
    while True:
        try:
            await asyncio.sleep(86400)
            days = int(os.getenv("ORDERS_RETENTION_DAYS", "30"))
            cutoff = datetime.now() - timedelta(days=days)
            s = SessionLocal()
            try:
                stmt = delete(QuantOrder).where(
                    and_(
                        QuantOrder.created_at < cutoff,
                        QuantOrder.status.in_([OrderStatus.executed, OrderStatus.failed, OrderStatus.expired])
                    )
                )
                s.execute(stmt)
                s.commit()
            finally:
                s.close()
        except Exception:
            pass

@router.post("/admin/stocks/update_all")
async def admin_update_all_stocks(http_req: Request):
    if not _verify_admin(http_req):
        raise HTTPException(status_code=403, detail="Forbidden")
    cnt_cn = update_cn_names_mairui_db()
    return {"status": "success", "cn": cnt_cn}
